﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;

namespace EntregaSO
{
    public class Ascensor
    {
        protected string nombre;
        protected const int maxUsuarios = 5;
        protected const int maxPeso = 400;
        protected readonly int pisosEdificio;
        protected readonly Edificio edificio;
        protected int pesoActual;

        public EstadisticasTimer estadisticasTiempoDeUso;
        public EstadisticasPeso estadisticasCarga;
        public EstadisticasPasajeros estadisticasPasajeros;
        public EstadisticasDistancia estadisticasDistancia;
        protected EscribirADisco escritor;

        public enum SentidoAscensor
        {
            Sube = 1,
            Baja = -1,
            Quieto = 0
        }
        protected List<Usuario> usuarios;
        public SortedList<int, SolicitudAscensor> SiguientesPisos;
        protected int pisoActual;
        protected SentidoAscensor sentido;
        protected Planificador planificador;

        public Ascensor(int pisosEdificio, Planificador planificador, string nombre)
        {
            estadisticasTiempoDeUso = new EstadisticasTimer("Tiempo de uso del ascensor " + nombre);
            estadisticasCarga = new EstadisticasPeso("Carga total del ascensor " + nombre);
            estadisticasPasajeros = new EstadisticasPasajeros("Cantidad de pasajeros total del ascensor " + nombre);
            estadisticasDistancia = new EstadisticasDistancia("Distancia total del ascensor " + nombre);
            escritor = planificador.Escritor;

            this.nombre = nombre;
            //this.t = new System.Windows.Forms.Timer();
            this.usuarios = new List<Usuario>();
            this.SiguientesPisos = new SortedList<int,SolicitudAscensor>();
            this.pisoActual = 0;
            this.sentido = SentidoAscensor.Quieto;
            this.pisosEdificio = pisosEdificio;
            this.planificador = planificador;
            this.pesoActual = 0;
        }

        public void Avanzar()
        {
            lock (this)
            {
                while (this.SiguientesPisos.Count == 0)
                {
                    Monitor.Wait(this);
                }
                //Monitor.Wait(this, 300);
            }
            switch (this.sentido)
            {
                case SentidoAscensor.Sube:
                    pisoActual++;
                    estadisticasDistancia.Avanzar();
                    break;
                case SentidoAscensor.Baja:
                    pisoActual--;
                    estadisticasDistancia.Avanzar();
                    break;
                case SentidoAscensor.Quieto:
                    break;
            }
            ProcesarPiso();
            FijarSentido();
        }
        
        

        public int Piso
        {
            get { return this.pisoActual; }
        }
        
        public SentidoAscensor Sentido
        {
            get { return this.sentido; }
        }
        
        /// <summary>
        /// Solicitud de piso desde dentro de un ascensor
        /// </summary>
        /// <param name="piso">Solicitud del piso al que se desea ir</param>
        public void SolicitarPiso(SolicitudAscensor solicitud)
        {
            lock (this)
            {
                if (!SiguientesPisos.ContainsKey(solicitud.PisoOrigen))
                {
                    SiguientesPisos.Add(solicitud.PisoOrigen, solicitud);
                }
            }
        }
        
        /// <summary>
        /// Llamada al ascensor desde afuera
        /// </summary>
        /// <param name="piso">Piso en el cual se origina la llamada</param>
        public virtual void LlamarAscensor(SolicitudAscensor solicitud)
        {
            lock (this)
            {
                if (!this.SiguientesPisos.ContainsKey(solicitud.PisoOrigen))
                {
                    this.SiguientesPisos.Add(solicitud.PisoOrigen, solicitud);
                }
                Monitor.PulseAll(this);
            }
        }
       
        /// <summary>
        /// Fija el sentido en el cual el ascensor se debe mover
        /// </summary>
        protected virtual void FijarSentido()
        {
            SentidoAscensor sentidoAnterior = this.sentido;
            if (this.SiguientesPisos.Count != 0)
            {
                int AuxKey;
                if (this.sentido == SentidoAscensor.Sube)
                {
                    AuxKey = this.SiguientesPisos.Last().Key;
                }
                else
                {
                    AuxKey = this.SiguientesPisos.First().Key;
                }
                if (AuxKey > this.pisoActual)
                {
                    this.sentido = SentidoAscensor.Sube;
                }
                else
                {
                    this.sentido = SentidoAscensor.Baja;
                }
            }
            else
            {
                this.sentido = SentidoAscensor.Quieto;
            }
            if (sentidoAnterior != this.sentido)
            {
                if (sentidoAnterior == SentidoAscensor.Quieto)
                {
                    this.estadisticasTiempoDeUso.EmpezarAContar();
                }
                else if (this.sentido == SentidoAscensor.Quieto)
                {
                    this.estadisticasTiempoDeUso.TerminarDeContar();
                }
            }
        }
        /// <summary>
        /// Devuleve el peso actual del ascensor
        /// </summary>
        public int PesoActual
        {
            get { return this.pesoActual; }
        }
        /// <summary>
        /// Sube al ascensor a los usuarios que estan en el piso
        /// </summary>
        private void SubirUsuarios()
        {
            Usuario uNuevo = null;
            bool hayUsuarios = true;
            Piso p = this.planificador.ObtenerPiso(this.pisoActual);

            while (hayUsuarios && this.usuarios.Count < 6)
            {
                uNuevo = p.ObtenerPrimero(maxPeso - this.PesoActual);
                if (uNuevo != null)
                {
                    this.usuarios.Add(uNuevo);
                    this.pesoActual += uNuevo.Peso;
                    this.SolicitarPiso(new SolicitudAscensor(uNuevo.PisoDestino));
                    estadisticasPasajeros.AumentarCantidadEnUno();
                    estadisticasCarga.AumentarCarga(uNuevo.Peso);
                    uNuevo.tiempoEnAscensor.EmpezarAContar();
                    uNuevo.tiempoEnPiso.TerminarDeContar();
                }
                else
                {
                    hayUsuarios = false;
                }
            }
            if (p.Usuarios.Count != 0)
            {
                this.planificador.CrearSolicitud(pisoActual);
            }
        }
        /// <summary>
        /// Quita los usuarios del ascensor que llegaron a su destino
        /// </summary>
        private void BajarUsuarios()
        {
            List<Usuario> usuariosRestantes = new List<Usuario>();
            foreach (Usuario u in usuarios)
            {
                if (u.PisoDestino != this.pisoActual)
                {
                    usuariosRestantes.Add(u);
                }
                else
                {
                    this.pesoActual -= u.Peso;
                    u.tiempoEnAscensor.TerminarDeContar();
                    escritor.Escribir.WriteLine(u.tiempoEnPiso.ImprimirResultados());
                    escritor.Escribir.WriteLine(u.tiempoEnAscensor.ImprimirResultados());
                    escritor.Escribir.Flush();
                }
            }
            usuarios = usuariosRestantes;
        }

        public void ProcesarPiso()
        {
            if (sentido != SentidoAscensor.Quieto)
            {
                lock (this)
                {
                    if (SiguientesPisos.Keys.Contains(pisoActual))
                    {
                        SiguientesPisos.Remove(pisoActual);
                        BajarUsuarios();
                        SubirUsuarios();
                    }
                }
                
            }
        }
    }
}
